crackmeUK 解析手引き その1


今までの asm 製 crackme とは違い、VC - MFC 製なので、チェッカの捕捉にちょっと時間が掛かると思います。
ところで皆さん、crackme_UK を解けた方はどれだけいらっしゃいますか?
今回は都合上、4回にわけて説明したいと思います。
まずデバッガチェック・UPX 対策部分のみの解説となります。


今回の crackme は『 eagle0wl's crackme 』と比べるとかなり難しいものとなっていますが、
今までの crackme を全て解けたのなら、この crackme_UK も出来るはずです。
皆さん、頑張りましょう。
この crackme ですが、デバッガ対策・ UPX 対策がなされているそうです。
本体を調べてみると UPX でパックされています。まずアンパックしてみましょう。

アンパックの手順はわかりますね。それではアンパックされた crackme を普通に起動してみましょう。

『Incorrect File. Down Load Again!!!』と表示されてしまいました。
アンパック前は問題なく起動できたのに、なぜこんなことになるのでしょうか?

ここでまず躓いてしまいました・・・。
これは恐らく実行ファイル(つまり自分自身)に対して改竄チェックを行っているのではないかと思われます。
ここから OllyDbg の出番です。 

crackme を読み込んでから、CreateFileA にブレークポイントを仕掛けてみましょう。(2つとも BP を仕掛ける)


004011FE   mov     ebx, dword ptr ds:[<&KERNEL32.CreateFileA>]
004141A7   call    near dword ptr ds:[<&KERNEL32.CreateFileA>]


さて、ブレークしましたか?
ブレークした箇所は

004011FE  |> 8B1D 80C14100  mov     ebx, dword ptr ds:[<&KERNEL32.CreateFileA>]

です。これは ebx レジスタに API,CreateFileA のアドレスを格納しているだけで
実行はまだ行っていません。その下をトレースしながら見てみると、


00401204  |. 6A 00          push    0                          ; /hTemplateFile = NULL
00401206  |. 68 80000000    push    80                         ; |Attributes = NORMAL
0040120B  |. 6A 03          push    3                          ; |Mode = OPEN_EXISTING
0040120D  |. 6A 00          push    0                          ; |pSecurity = NULL
0040120F  |. 6A 03          push    3                          ; |ShareMode = FILE_SHARE_READ|FILE_SHARE_WRITE
00401211  |. 8D4C24 20      lea     ecx, dword ptr ss:[esp+20] ; |
00401215  |. 68 000000C0    push    C0000000                   ; |Access 


典型的な SoftICE チェッカですね。
UPX 対策箇所を探すつもりでしたが、デバッガチェック箇所を探し当てることが出来たみたいです。
ここで注目して貰いたいのは、CreateFileA にブレークポイントを仕掛けたはずなのに
CreateFileA がコールされているアドレス 0040121B にはブレークポイントが仕掛けられていません。
よく見ると call  near ebx と、ebx レジスタ内部の値を参照しています。

ebx の値が決まるまで CreateFileA が呼び出されるとは断定できないため、ブレークポイントが
仕掛けられなかったのだと思われます。
(でも右側に CreateFileA と引数の表示がされているのはなぜでしょうか。誰か知ってる方教えて下さい)

それではデバッガチェック部分を当たってみましょう。


0040121A  |. 51             push    ecx                        ; |FileName = "\\.\ICE"  <-- 注目!!
0040121B  |. FFD3           call    near ebx                   ; \CreateFileA           <-- BP は仕掛けられていない!


0040121B まで各自 F8 を押してそこまで進めて下さい。そうすると0040121B でブレークしているはずです。
それではデバッガチェック部分を当たってみましょう。


0040121B  |. FFD3           call    near ebx                   ; \CreateFileA
0040121D  |. 83F8 FF        cmp     eax, -1
00401220  |. 74 10          je      short CRACKME_.00401232


皆さんは OllyDbg を使用しているはずなので、SoftICE は検出されず je 命令でジャンプするはずですが、
ここではあえてゼロフラグを反転させることで SoftICE が検出されたことにして下さい。

SoftICE が検出された場合は、戻り値 1 をセットして ret 命令で関数を抜けています。
ではなぜ戻り値は 1 とわかるのでしょうか?
je 命令でジャンプしなかったときに実行される命令を見てみましょう。


0040122B  |. B0 01          mov     al, 1     ; 戻り値 1 をセット
0040122D  |. 5B             pop     ebx
0040122E  |. 83C4 20        add     esp, 20
00401231  |. C3             retn


なぜ戻り値がわかるのかと言いますと、殆どのコンパイラは関数の戻り値を eax レジスタに
格納するようにしているからです(★解析を行う上でのポイント)。
ここで SoftICE が検出されなかったら SoftICE(NT版)のチェッカがあるのですが、これは自分で
確認してみて下さい。
今回は ゼロフラグを反転させて SoftICE が検出されたことにしました。関数を抜けるとここに現れます。


004010CF   . E8 BC000000    call    CRACKME_.00401190     <-- SoftICE チェック関数
004010D4   . 84C0           test    al, al                <-- ここに居る
004010D6   . 74 12          je      short CRACKME_.004010EA
004010D8   . 68 2C214200    push    CRACKME_.0042212C  ;  ASCII "I find "soft ice". Kill Soft Ice !!!!!"


al の値は 1 で test al, al の結果、ゼロフラグが立たないので次のジャンプ命令が実行されず、
SoftICE 検出を示す文字列が参照されてしまいます。
ここでは、 test  al, al を xor  eax, eax に変更することで SoftICE チェッカを殺すことにしましょう。
SoftICE チェッカを抜けたら


004010EA   > E8 E1020000    call    CRACKME_.004013D0     <- ここに来る
004010EF   . 84C0           test    al, al
004010F1   . 74 7F          je      short CRACKME_.00401172


ここに来るはずです。
call 分の後に比較・分岐があるので非常に怪しいですが、とりあえず F8 で素通りしてみましょう。
すると、ブレークポイントに引っかかって停止しました。CreateFileA です。
ブレークしましたか?

一体どんなファイルを調べようとしているのでしょうか。デバッグ画面右下のスタック領域部分を見て下さい。


0065F700   0065F870  |FileName = "C:\(中略)\abc.dll"
0065F704   80000000  |Access = GENERIC_READ


CreateFileA の引数が見えます。
非常に読みやすくて良いですね。OllyDbg 最強!

参照されているファイルは abc.dll です。この abc.dll とは何なのかは疑問ですが、とりあえず置いておきます。
とりあえず F8 を連打してトレースを続けてみましょう。
これであの dll ファイルを参照しているんですね


004014DD  |. E8 EA2B0100    call    CRACKME_.004140CC         ; \CRACKME_.004140CC
004014E2  |. 85C0           test    eax, eax                  <-- ココに出る
004014E4  |. 0F84 99000000  je      CRACKME_.00401583
004014EA  |. B9 41000000    mov     ecx, 41


ここの関数はちょっと長めですが、トレースはしないでズズズーっと下にスクロールしてみましょう。
すると、

00401583  |> 68 88214200    push    CRACKME_.00422188         ;  ASCII "Incorrect File. Down Load Again!!!"

なんと、エラー表示の文字列を見つけてしまいました。
改竄チェックルーチンで間違いなさそうです。

ここまで追いつかない方は、文字列参照でショートカットできます。
今回は勉強の為、「最初から文字列参照でやればいいだろ!!」とかの突っ込みは勘弁して下さい。


さて、エラー表示の文字列参照部分があるということは、比較分岐命令が有るはずです。
上へ上へと眺めてみると、一ヶ所だけありました。


00401542  |. 3BD0           cmp     edx, eax
00401544  |. 75 3D          jnz     short CRACKME_.00401583


ここは文字列参照されているアドレス 00401583 にカーソルを合わせて CTRL + R でもいいです。
非常に怪しいですね。edx と eax の値が違っているとジャンプしてしまい、エラー表示の文字列が
参照されてしまいますので、cmp  edx, eax を cmp  eax, eax に変更してしまいましょう。
変更はできましたか?

では【変更した命令部分に BP を仕掛けてから】 F9 を押して実行してみましょう。
すると、CreateFileA でブレークしました。スタックを参照してみましょう。すると


0065F684   0065F974  |FileName = "C:\(中略)\CRACKME.EXE"
0065F688   80000000  |Access = GENERIC_READ


自分自身が参照されています。改竄チェックなので当然ですね。
ここから読み込みが始まるのですが、この内部を説明すると非常に骨が折れるので割愛します。
興味のある方は自分で調べてみて下さい。
F9 を押してみると、先ほどブレークポイントを仕掛けた変更済の比較命令でブレークするはずです。


00401542     3BC0           cmp     eax, eax      <- ここでブレーク
00401544  |. 75 3D          jnz     short CRACKME_.00401583


ここでは変更済なのでジャンプせず、UPX 対策は突破されたことになります。
eax レジスタと、比較されるはずだった edx レジスタの中身に注目して下さい。
eax = B2E8B9FC, edx = ACCF4C2C  となっていました。 abc.dll が参照されたことを
思い出して下さい。ちょっと abc.dll の中身を参照してみましょう。


[offset]: +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F  0123456789ABCDEF
----------------------------------------------------------------------------
00000000: 2C 4C CF AC                                      ,Lマャ



x86 系 CPU はリトルエンディアン形式なので逆読みすると ACCF4C2C 、
これは edx レジスタに入っていた値と同じです!!

ファイルの改竄チェックで一番有名なものに CRC(巡回冗長符号)というものがあります。
本 crackme の UPX 対策チェッカで用いられているのは CRC です。
これはファイル等まとまったデータからある一定の値(大抵 32bit 分の値)を生成するもので、
今回は、あらかじめ用意されている UPX 圧縮された改竄前の crackme から生成された CRC 値と、
今回読み込んだ実行ファイルから生成された CRC 値を比較していたわけです。

しかし、なぜ abc.dll なるファイルが必要だったのでしょうか?
これは、CRC 値作成にあたり、実行ファイルの何処から何処までを対象にしているかが問題なのです。
もし実行ファイルの中に改竄前のファイルから生成された CRC 値 を埋め込んでいるのだとすると、
その CRC 部分を改竄チェックの対象に含めてしまうと値が滅茶苦茶になってしまいます。
実行ファイルに埋め込むとなると実装が難しくなるので、別ファイルにしたというわけですね。

CRC についての講釈はここまでにして、現段階でのパッチは以下のようになります。



FILENAME CrackMe.exe
* Anti UPX checker
00001543: D0 C0
* Anti-Anti SoftICE
000010D4: 84 33



この手の Anti-Debug, Anti-UPX 解除は一般的な国産ソフトでは殆ど見られません。
Anti-UPX については海外の crackme では見られるようです。
Anti-Debug が使われている代表的なソフトは、個人情報を送信していることが問題となった
某 ftp ソフトです。
今回の Anti-Debug 処理と同じ方法が使われていたのですが、
その対象がデバッガではなく、キージェネに対してでした。
つまり、キージェネ自体をブラックリスト化されています。

立ち上げていなければチェックのしようがないので、キーを生成してからそのキーをメモしてから 
Keygen を終了し、対象ソフトを立ち上げれば OK のようです。


さて、本題に戻りましょう。CRC チェッカ部分を殺す場所で停止していますが、


00401542     3BC0           cmp     eax, eax      <- edx, edx を書き換えている
00401544  |. 75 3D          jnz     short CRACKME_.00401583


F8 連打で(CTRL + F9 でもよい) ret 命令まで進み関数を抜けてみましょう。すると、


004010EA   > E8 E1020000    call    CRACKME_.004013D0        <-- ファイル改竄チェック関数
004010EF   . 84C0           test    al, al                   <-- ここに出ます
004010F1   . 74 7F          je      short CRACKME_.00401172
004010F3   . 8D4424 0C      lea     eax, dword ptr ss:[esp+C]
004010F7   . C74424 0C 0100>mov     dword ptr ss:[esp+C], 1
004010FF   . 50             push    eax                              ; /lParam
00401100   . 68 C0124000    push    CRACKME_.004012C0                ; |Callback = CRACKME_.004012C0
00401105   . FF15 38C44100  call    near dword ptr ds:[<&USER32.Enum>; \EnumWindows
0040110B   . 8B4424 0C      mov     eax, dword ptr ss:[esp+C]        <- ここに BP を仕掛けてね


矢印が指している部分に出ました。ここでは内部にパッチを当てたので、ジャンプせず処理が継続されます。
その下には EnumWindows が見えます。
実はこれがにっくき OllyDbg チェックルーチンなのです。
EnumWindows の引数 Callback = CRACKME_.004012C0 とありますが、これが EnumWindows が呼び出そうとしている
コールバック関数の開始アドレスです。
crackme #15 でも同じやつがありましたね。

F7 でトレースしてもコールバック関数内部にはジャンプしないので、CTRL + G でアドレス 004012C0 に
移動して BP を仕掛けてください。
でもその前に EnumWindows を抜けた後もトレースを続けたいので、アドレス 0040110B に BP を仕掛けてから
上記の動作を行って下さい。
実行するとコールバック関数内部で止まるはずです。
F8 を連打してトレースすると、#15 と同じように現在デスクトップ上に存在するウィンドウのタイトルバーの
文字列を取得して、NG ワードチェックを行っているようです。


00401304   . 8B15 B0204200  mov     edx, dword ptr ds:[4220B0]  ;  CRACKME_.004220E4  <- "Olly" が入る
0040130A   . 8D4424 04      lea     eax, dword ptr ss:[esp+4]
0040130E   . 52             push    edx
0040130F   . 50             push    eax
00401310   . E8 8B350000    call    CRACKME_.004048A0           ; タイトルバー文字列から "Olly" をサーチ
00401315   . 83C4 08        add     esp, 8
00401318   . 85C0           test    eax, eax
0040131A   . 5F             pop     edi
0040131B   . 75 6D          jnz     short CRACKME_.0040138A     ; 見つかったらアウト


最初のチェックはこんな感じです。
同様に、"W32", "Dump", "Moniter" をタイトルバーから探し、もし一致したらデバッガが検出されたことになり、
エラー処理に飛ばすようにしています。"Moniter" の綴りが違うような気がしますが UK さんに確認したところ、
WebCheckMonitor というやつが常駐していると誤認されてしまうため、わざと変えてみたそうです。

結果、test  eax, eax を xor  eax, eax に変更すればよさそうです。変える場所は計4ヶ所。
00401318, 00401331, 00401348, 00401360 です。(命令はいずれも test  eax, eax)
変更が完了したらコールバック関数の先頭に仕掛けた BP (アドレス 004012C0) は解除して下さい。
EnumWindows が捕まえたウィンドウの数だけ処理を繰り返すのでそのたびにブレークして大変だからです。

さて、F9 を押すと、先ほど BP を仕掛けた EnumWindows を抜けたところの命令でブレークします。

いよいよ最後のデバッガチェック部分です。


0040110B   . 8B4424 0C      mov     eax, dword ptr ss:[esp+C] <-- ここで停止
0040110F   . 85C0           test    eax, eax
00401111   . 7C 5F          jl      short CRACKME_.00401172   <-- デバッガチェックを潰したのでジャンプせず
00401113   . FFD7           call    near edi                  <-- これは一体??


非常に気になる命令は call  near edi です。これは一体なんでしょう?
ここでちょっとレジスタについての、いい話をしておきましょう。

普通 call 命令を抜けると各種レジスタの値は変わってしまうのですが(eax レジスタには戻り値が格納されている)、
ebx, esi, edi レジスタの値については call 命令内部で push'n pop されているためその値は保持されています。
ですから、頻繁に値の変わる eax, ecx, edx レジスタとは違って ebx, esi, edi レジスタに入っています。
その値は、関数内部で長く使われるもの、すなわち結構重要そうであると解釈できるはずです(★かなり重要)。
これを覚えておくと、EASY のパス出しで結構楽をする事ができるので要チェック!

話は戻りまして、call  near edi についてですが、この関数内部で edi レジスタに値を移している部分を
さかのぼって探してみましょう。
すると、ありました。デバッガチェック部分の最初の方です。


004010C5   . 8B3D 24C24100  mov     edi, dword ptr ds:[<&KERNEL32.GetTickCount>]
004010CB   . FFD7           call    near edi                 ; [GetTickCount]
004010CD   . 8BD8           mov     ebx, eax                 ; ebx に時間情報を格納


edi の正体は GetTickCount でした。この関数は Windows が起動されてから経過した時間を
ミリ秒単位で返すAPI です(引数無し)。ebx に時間情報を格納していますが、edi の正体が分かったところで
先ほどの  call  near edi  をもう一度見てましょう。


00401113   . FFD7           call    near edi                  <-- GetTickCount(2度目)
00401115   . 2BC3           sub     eax, ebx                 ; eax - ebx(ebx には時間情報)
00401117   . 83F8 64        cmp     eax, 64                  ; 64h = 100
0040111A   . 76 0F          jbe     short CRACKME_.0040112B  ; eax <= 100 なら OK
0040111C   . 68 10214200    push    CRACKME_.00422110        ;  ASCII "Too Slow. Stop Debugger!!!!"


GetTickCount で時刻情報を取得し、その値は eax レジスタに格納されています。
次に sub  eax, ebx が行われているわけですが、ebx にはデバッガチェックの最初の方で
GetTickCount(1度目)で取得した値が入っています。

ということは、【GetTickCount の1度目と2度目の間にある命令の実行時間を算出している】
ということが分かるはずです。


そして cmp  eax, 64 で、実行時間が 100 ミリ秒以内であるかどうかを調べ、そうであれば
デバッガチェックは終了となります。これは一体何を調べようとしているのでしょうか?
よほど遅い CPU でもなければデバッガチェックルーチンは 100 ミリ秒以内で実行できるはずです。
しかし例外があります。それはデバッガでトレースしている場合です。トレースするということは
実行が一時停止状態になるわけですから、実行時間は 100 ミリ秒をゆうに超えてしまいます。

ということは、これはアンチトレース処理であるということができます。
時間差攻撃を潰す訳ですね。
この対処方法は簡単です。cmp  eax, 64 を xor  eax, eax に変えるだけです。
これで、ようやくデバッガチェック部分は終了しました。

以上をまとめたパッチは、



FILENAME CrackMe.exe
* Anti UPX checker
00001543: D0 C0
* Anti-Anti SoftICE
000010D4: 84 33
* Anti-Anti Debugger
00001318: 85 33
00001331: 85 33
00001348: 85 33
00001360: 85 33
00001378: 85 33
* Anti-Anti Trace
00001115: 2B 33
00001116: C3 C0



これは一例です。

とりあえず今回はデバッガチェック部分のみで終わります。

次回予定、・EASY の攻略、・NORMAL の攻略、・DIFFICULT の分析
      DIFFICULT だけ "分析" となっていますが、その理由はやってみればわかるはずです。